﻿/*
 * QWine Queue Machine (Special build for Immigration Office)
 * Copyright (C) 2010 Wish Enterprise Development Team

 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * Contact Us:
 * E-Mail   : contact@wishenterprise.com
 * 
 * */

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace QWRepository
{
    /// <summary>
    /// BitmapToRegion
    /// </summary>
    public class BitmapToRegion
    {
        /// <summary>
        /// This method uses only safe code (no pointers) to scan an imagine.
        /// and create a region from it
        /// </summary>
        /// <param name="inputBmp">The input BMP.</param>
        /// <param name="transperancyKey">The transperancy key.</param>
        /// <param name="tolerance">The tolerance.</param>
        /// <returns></returns>
        public static Region GetRegion(Bitmap inputBmp, Color transperancyKey, int tolerance)
        {
            //Stores all the rectangles for the region
            var path = new GraphicsPath();

            //Scan the image
            for (int x = 0; x < inputBmp.Width; x++)
            {
                for (int y = 0; y < inputBmp.Height; y++)
                {
                    if (!ColorsMatch(inputBmp.GetPixel(x, y), transperancyKey, tolerance))
                        path.AddRectangle(new Rectangle(x, y, 1, 1));
                }
            }

            //Create the Region
            var outputRegion = new Region(path);

            //Clean up
            path.Dispose();

            //Finish
            return outputRegion;
        }

        /// <summary>
        /// Colorses the match.
        /// </summary>
        /// <param name="color1">The color1.</param>
        /// <param name="color2">The color2.</param>
        /// <param name="tolerance">The tolerance.</param>
        /// <returns></returns>
        private static bool ColorsMatch(Color color1, Color color2, int tolerance)
        {
            if (tolerance < 0) tolerance = 0;
            return Math.Abs(color1.R - color2.R) <= tolerance &&
                   Math.Abs(color1.G - color2.G) <= tolerance &&
                   Math.Abs(color1.B - color2.B) <= tolerance;
        }

        /// <summary>
        /// Colorses the match.
        /// </summary>
        /// <param name="pixelPtr">The pixel PTR.</param>
        /// <param name="color1">The color1.</param>
        /// <param name="tolerance">The tolerance.</param>
        /// <returns></returns>
        private static unsafe bool ColorsMatch(uint* pixelPtr, Color color1, int tolerance)
        {
            if (tolerance < 0) tolerance = 0;

            //Convert the pixel pointer into a color
            var a = (byte) (*pixelPtr >> 24);
            var r = (byte) (*pixelPtr >> 16);
            var g = (byte) (*pixelPtr >> 8);
            var b = (byte) (*pixelPtr >> 0);
            Color pointer = Color.FromArgb(a, r, g, b);

            //Each value between the two colors cannot differ more than tolerance
            return Math.Abs(color1.A - pointer.A) <= tolerance &&
                   Math.Abs(color1.R - pointer.R) <= tolerance &&
                   Math.Abs(color1.G - pointer.G) <= tolerance &&
                   Math.Abs(color1.B - pointer.B) <= tolerance;
        }

        /// <summary>
        ///Uses pointers to scan through the bitmap a LOT faster
        /// Make sure to check "Allow unsafe code" in the project properties
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        /// <param name="transparencyKey">The transparency key.</param>
        /// <param name="tolerance">The tolerance.</param>
        /// <returns></returns>
        public static unsafe Region GetRegionFast(Bitmap bitmap, Color transparencyKey, int tolerance)
        {
            //Bounds
            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF boundsF = bitmap.GetBounds(ref unit);
            var bounds = new Rectangle((int) boundsF.Left, (int) boundsF.Top,
                                       (int) boundsF.Width, (int) boundsF.Height);

            var yMax = (int) boundsF.Height;
            var xMax = (int) boundsF.Width;

            //Transparency Color
            if (tolerance <= 0) tolerance = 1;


            //Lock Image
            BitmapData bitmapData = bitmap.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            var pixelPtr = (uint*) bitmapData.Scan0.ToPointer();

            //Stores all the rectangles for the region
            var path = new GraphicsPath();

            //Scan the image, looking for lines that are NOT the transperancy color
            for (int y = 0; y < yMax; y++)
            {
                var basePos = (byte*) pixelPtr;

                for (int x = 0; x < xMax; x++, pixelPtr++)
                {
                    //Go on with the loop if its transperancy color

                    if (ColorsMatch(pixelPtr, transparencyKey, tolerance))
                        continue;

                    //Line start
                    int x0 = x;

                    //Find the next transparency colored pixel
                    while (x < xMax && !ColorsMatch(pixelPtr, transparencyKey, tolerance))
                    {
                        x++;
                        pixelPtr++;
                    }

                    //Add the line as a rectangle
                    path.AddRectangle(new Rectangle(x0, y, x - x0, 1));
                }

                //Go to next line
                pixelPtr = (uint*) (basePos + bitmapData.Stride);
            }

            //Create the Region
            var outputRegion = new Region(path);

            //Clean Up
            path.Dispose();
            bitmap.UnlockBits(bitmapData);

            return outputRegion;
        }
    }
}